Skip to content

feat!: extract ScriptGoogleMapsStaticMap as standalone component#673

Merged
harlan-zw merged 4 commits intomainfrom
worktree-google-maps-static-map
Mar 26, 2026
Merged

feat!: extract ScriptGoogleMapsStaticMap as standalone component#673
harlan-zw merged 4 commits intomainfrom
worktree-google-maps-static-map

Conversation

@harlan-zw
Copy link
Copy Markdown
Collaborator

🔗 Linked issue

Related to #594

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

The static map placeholder was tightly coupled inside ScriptGoogleMaps, creating several issues: percentage width/height produced invalid size parameters for the Static Maps API, there was no way to disable the placeholder, and placeholderOptions/placeholderAttrs/aboveTheFold were a confusing three-prop surface for one concept.

This extracts the static map into a standalone <ScriptGoogleMapsStaticMap> component that can be used independently (store locators, contact pages, directions previews) or dropped into the #placeholder slot of ScriptGoogleMaps.

The new component auto-measures its rendered pixel dimensions on mount, so percentage-based CSS sizing works correctly. It also supports multiple markers/paths via string arrays, accepts both raw Static Maps API style strings and JS API MapTypeStyle[] objects, and handles the proxy/direct API key logic.

⚠️ Breaking Changes

Removed from ScriptGoogleMaps:

  • placeholderOptions prop
  • placeholderAttrs prop
  • aboveTheFold prop
  • PlaceholderOptions exported type
  • Built-in static map <img> in the default #placeholder slot

The #placeholder slot is now empty by default. The slot prop placeholder (URL string) is no longer passed.

📝 Migration

<!-- Before -->
<ScriptGoogleMaps
  :center="center"
  :zoom="7"
  above-the-fold
  :placeholder-options="{ maptype: 'satellite' }"
  :placeholder-attrs="{ class: 'rounded' }"
/>

<!-- After -->
<ScriptGoogleMaps :center="center" :zoom="7">
  <template #placeholder>
    <ScriptGoogleMapsStaticMap
      :center="center"
      :zoom="7"
      loading="eager"
      maptype="satellite"
      :img-attrs="{ class: 'rounded' }"
    />
  </template>
</ScriptGoogleMaps>

<!-- Or disable placeholder entirely (just don't use the slot) -->
<ScriptGoogleMaps :center="center" :zoom="7" />

<!-- Standalone usage (no interactive map) -->
<ScriptGoogleMapsStaticMap
  center="51.95,19.13"
  :zoom="7"
  width="100%"
  height="300"
/>

Note: registry-types.json needs regeneration via pnpm generate:types after merge.

…omponent

BREAKING CHANGE: Remove `placeholderOptions`, `placeholderAttrs`, and `aboveTheFold` props from ScriptGoogleMaps. The built-in static map placeholder image is no longer rendered by default.

Use the new `ScriptGoogleMapsStaticMap` component in the `#placeholder` slot instead:

```vue
<ScriptGoogleMaps :center="center" :zoom="7">
  <template #placeholder>
    <ScriptGoogleMapsStaticMap :center="center" :zoom="7" loading="eager" />
  </template>
</ScriptGoogleMaps>
```

The new component can also be used standalone (store locators, contact pages, etc.) and fixes the percentage width/height bug that produced invalid Static Maps API `size` parameters by auto-measuring rendered dimensions on mount.
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
scripts-playground Error Error Mar 26, 2026 5:47am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0653c7c4-fba3-43d4-b225-736e116c5cd2

📥 Commits

Reviewing files that changed from the base of the PR and between f64da30 and 3ad3235.

📒 Files selected for processing (2)
  • docs/content/docs/4.migration-guide/1.v0-to-v1.md
  • docs/content/scripts/google-maps/2.api/1b.static-map.md

📝 Walkthrough

Walkthrough

Removed placeholder-related props and logic from packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue (deleted props: aboveTheFold, placeholderOptions, placeholderAttrs; removed Static Maps placeholder generation and fallback rendering; #placeholder slot no longer provides a URL). Added a new ScriptGoogleMapsStaticMap.vue component that renders Google Static Maps images with typed props, proxy support, size resolution (explicit size, client measurement, SSR fallbacks), computed src exposed, and server-side link hints. Replaced manual pixel style strings with a local toCssUnit helper. Playground examples and docs/migration guide updated accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: extracting the static map placeholder into a standalone component, which is the primary objective of the PR.
Description check ✅ Passed The description comprehensively explains the motivation, changes, breaking changes, and provides clear migration examples showing how users should adapt their code.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-google-maps-static-map

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue`:
- Around line 110-112: The component currently prefers the runtime proxy
unconditionally and strips a component-level key; change the logic so an
explicit props.apiKey always wins: compute apiKey = props.apiKey ??
scriptRuntimeConfig('googleMaps')?.apiKey and only choose the proxy (proxyConfig
/ googleStaticMapsProxy) when props.apiKey is not provided and the proxy is
actually enabled/usable; update the proxy selection/use sites (references:
apiKey, props.apiKey, proxyConfig, scriptRuntimeConfig, googleStaticMapsProxy)
and apply the same guard in the other similar branch mentioned.
- Around line 32-34: The TypeScript type for the static map "scale" prop
currently allows 1 | 2 | 4 which is invalid for Google Static Maps; update the
prop/type definition for "scale" (in ScriptGoogleMapsStaticMap.vue) to only
allow 1 | 2, and update any related validation/usage sites (prop default, prop
validator, or URL-building code that reads "scale") to reflect the two allowed
values so callers cannot generate scale=4 URLs.
- Around line 122-129: Auto-measured and derived sizes are being assigned
directly to measuredSize (in the onMounted block and the derived-size branch
around lines 176–184), which can produce dimensions exceeding Google Static Maps
limits; update the logic in onMounted and the derived-size computation to
clamp/scale down the measured width/height proportionally to fit within the
allowed max (use 640x640 as the base max or 1280x1280 when props.scale === 2)
while preserving aspect ratio, and only set measuredSize when the clamped
dimensions are used; keep props.size as the override/escape hatch and reference
measuredSize, rootEl, props.size and props.scale when implementing the clamp.

In `@playground/pages/third-parties/google-maps/nuxt-scripts.vue`:
- Around line 27-33: The checked-in literal Google Maps API key should be
removed and replaced with a runtime-config/env binding: add a shared key
variable (e.g., googleMapsApiKey) sourced from runtime config or an env var
(useRuntimeConfig()/process.env.NUXT_PUBLIC_GOOGLE_MAPS_API_KEY) and bind it to
the components instead of the hard-coded string; update the
ScriptGoogleMapsStaticMap component's api-key prop to use
:api-key="googleMapsApiKey" and do the same for the other map component(s) on
the page so both reuse the single runtime-config-sourced value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e9f14cb-119c-4bdf-9234-530e5894d314

📥 Commits

Reviewing files that changed from the base of the PR and between 379d290 and 335a953.

📒 Files selected for processing (13)
  • packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMaps.vue
  • packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue
  • playground/pages/third-parties/google-maps/center.vue
  • playground/pages/third-parties/google-maps/emit-test.vue
  • playground/pages/third-parties/google-maps/geojson-test.vue
  • playground/pages/third-parties/google-maps/markers.vue
  • playground/pages/third-parties/google-maps/null.vue
  • playground/pages/third-parties/google-maps/nuxt-scripts.vue
  • playground/pages/third-parties/google-maps/overlay-animated.vue
  • playground/pages/third-parties/google-maps/overlay-popup.vue
  • playground/pages/third-parties/google-maps/query.vue
  • playground/pages/third-parties/google-maps/sfcs.vue
  • playground/pages/third-parties/google-maps/styled.vue
💤 Files with no reviewable changes (10)
  • playground/pages/third-parties/google-maps/geojson-test.vue
  • playground/pages/third-parties/google-maps/styled.vue
  • playground/pages/third-parties/google-maps/query.vue
  • playground/pages/third-parties/google-maps/markers.vue
  • playground/pages/third-parties/google-maps/center.vue
  • playground/pages/third-parties/google-maps/overlay-animated.vue
  • playground/pages/third-parties/google-maps/overlay-popup.vue
  • playground/pages/third-parties/google-maps/sfcs.vue
  • playground/pages/third-parties/google-maps/emit-test.vue
  • playground/pages/third-parties/google-maps/null.vue

Comment on lines +32 to +34
* Device pixel ratio for the static map image (1, 2, or 4).
*/
scale?: 1 | 2 | 4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Google Maps Static API scale parameter documentation

💡 Result:

The scale parameter in the Google Maps Static API is an optional URL parameter that controls the pixel density of the returned map image. It is particularly useful for high-resolution displays like Retina screens. Key details: - Default value: 1 - Accepted values: 1 or 2 (doubles the pixels while keeping the same map coverage area and detail level) - Interaction with size: The final image dimensions are size width/height multiplied by scale (e.g., size=400x300&scale=2 produces an 800x600 pixel image). - Purpose: Provides sharper images on high-DPI screens without changing the zoom or visible area; roads and labels remain legible. - Limits: Standard max size 640x640 pixels (or 1280x1280 with scale=2); larger sizes may require support contact. - Note on markers: There is a separate scale descriptor for markers (1,2,4) prefixed as 'scale:N' in the markers parameter. Example URL: https://maps.googleapis.com/maps/api/staticmap?center=Brooklyn+Bridge,New+York,NY&zoom=13&size=600x300&scale=2&key=YOUR_API_KEY Official documentation is in the "Get Started" guide at developers.google.com/maps/documentation/maps-static/start, under URL Parameters and Scale Values section.

Citations:


Restrict scale to values the Static Maps API actually accepts.

The Google Maps Static API only accepts scale=1 or scale=2. Allowing scale=4 in the type definition enables type-safe code to generate rejected image URLs.

Suggested fix
-  /**
-   * Device pixel ratio for the static map image (1, 2, or 4).
-   */
-  scale?: 1 | 2 | 4
+  /**
+   * Device pixel ratio for the static map image (1 or 2).
+   */
+  scale?: 1 | 2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue`
around lines 32 - 34, The TypeScript type for the static map "scale" prop
currently allows 1 | 2 | 4 which is invalid for Google Static Maps; update the
prop/type definition for "scale" (in ScriptGoogleMapsStaticMap.vue) to only
allow 1 | 2, and update any related validation/usage sites (prop default, prop
validator, or URL-building code that reads "scale") to reflect the two allowed
values so callers cannot generate scale=4 URLs.

Comment on lines +122 to +129
onMounted(() => {
if (props.size || !rootEl.value)
return
const { offsetWidth, offsetHeight } = rootEl.value
if (offsetWidth > 0 && offsetHeight > 0) {
measuredSize.value = `${offsetWidth}x${offsetHeight}`
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "ScriptGoogleMapsStaticMap.vue" -type f

Repository: nuxt/scripts

Length of output: 137


🏁 Script executed:

cat -n ./packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue | sed -n '110,190p'

Repository: nuxt/scripts

Length of output: 3483


🌐 Web query:

Google Maps Static API size limits 640x640

💡 Result:

The Google Maps Static API has a standard maximum image size of 640x640 pixels. With scale=2, this effectively allows up to 1280x1280 pixels (twice the pixels while retaining the same coverage area). Sizes larger than 640x640 (or 1280x1280 with scale=2) require contacting Google support for approval on a case-by-case basis, with a maximum possible size of 2048x2048 pixels if approved. Maps smaller than 180 pixels wide show a reduced Google logo. The size parameter is specified as widthxheight (e.g., size=640x640). URL length is limited to 16384 characters.

Citations:


Clamp auto-measured sizes before assigning to size.

The measured/derived branches pass raw CSS pixels directly to the Google Maps Static API, which has a standard limit of 640×640 pixels (or 1280×1280 with scale=2). Larger dimensions require explicit approval. A standalone width: 100% map can easily exceed this limit on desktop and generate a broken URL. Keep explicit props.size as the escape hatch, but scale down auto-measured and derived values proportionally to fit within the standard constraints.

Clamping function to enforce size limits
+function clampAutoSize(width: number, height: number, max = 640): string {
+  const ratio = Math.min(1, max / Math.max(width, height))
+  return `${Math.max(1, Math.floor(width * ratio))}x${Math.max(1, Math.floor(height * ratio))}`
+}
+
 onMounted(() => {
   if (props.size || !rootEl.value)
     return
   const { offsetWidth, offsetHeight } = rootEl.value
   if (offsetWidth > 0 && offsetHeight > 0) {
-    measuredSize.value = `${offsetWidth}x${offsetHeight}`
+    measuredSize.value = clampAutoSize(offsetWidth, offsetHeight)
   }
 })
@@
   // SSR fallback: derive from width/height if both are pixel values
   if (isPixelValue(props.width) && isPixelValue(props.height))
-    return `${Number.parseInt(String(props.width))}x${Number.parseInt(String(props.height))}`
+    return clampAutoSize(Number.parseInt(String(props.width)), Number.parseInt(String(props.height)))
   return '640x400'
 })

Also applies to: lines 176-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue`
around lines 122 - 129, Auto-measured and derived sizes are being assigned
directly to measuredSize (in the onMounted block and the derived-size branch
around lines 176–184), which can produce dimensions exceeding Google Static Maps
limits; update the logic in onMounted and the derived-size computation to
clamp/scale down the measured width/height proportionally to fit within the
allowed max (use 640x640 as the base max or 1280x1280 when props.scale === 2)
while preserving aspect ratio, and only set measuredSize when the clamped
dimensions are used; keep props.size as the override/escape hatch and reference
measuredSize, rootEl, props.size and props.scale when implementing the clamp.

Comment on lines +27 to +33
<ScriptGoogleMapsStaticMap
:center="mapOptions.center"
:width="640"
:height="500"
loading="eager"
api-key="AIzaSyAOEIQ_xOdLx2dNwnFMzyJoswwvPCTcGzU"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove the checked-in Google Maps key from the playground.

Line 32 adds another hard-coded Maps credential to the repository. Even if it's restricted, committed cloud keys leak into forks, preview deployments, and published artifacts. Please switch this page to a runtime-config/env binding and reuse that value for both map components instead of duplicating the literal inline.

🧰 Tools
🪛 Betterleaks (1.1.1)

[high] 32-32: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playground/pages/third-parties/google-maps/nuxt-scripts.vue` around lines 27
- 33, The checked-in literal Google Maps API key should be removed and replaced
with a runtime-config/env binding: add a shared key variable (e.g.,
googleMapsApiKey) sourced from runtime config or an env var
(useRuntimeConfig()/process.env.NUXT_PUBLIC_GOOGLE_MAPS_API_KEY) and bind it to
the components instead of the hard-coded string; update the
ScriptGoogleMapsStaticMap component's api-key prop to use
:api-key="googleMapsApiKey" and do the same for the other map component(s) on
the page so both reuse the single runtime-config-sourced value.

@harlan-zw harlan-zw changed the title feat!: extract ScriptGoogleMapsStaticMap as standalone component feat!: extract ScriptGoogleMapsStaticMap as standalone component Mar 26, 2026
- Restrict scale prop to 1 | 2 (matches Static Maps API)
- Explicit apiKey prop now takes priority over proxy
- Clamp auto-measured sizes to 640x640 API limit
- Remove hardcoded API key from playground
- Add ScriptGoogleMapsStaticMap API doc page
- Update ScriptGoogleMaps docs for new placeholder pattern
- Add Google Maps breaking changes to v0-to-v1 migration guide
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
docs/content/scripts/google-maps/2.api/1b.static-map.md (1)

81-84: Address passive voice to fix pipeline warnings.

Consider rewriting to use active voice:

📝 Suggested rewrite
-When `googleMaps` is configured in `scripts.registry`, a server-side proxy is automatically enabled. The component routes requests through the proxy unless an explicit `apiKey` prop is provided.
+Configuring `googleMaps` in `scripts.registry` automatically enables a server-side proxy. The component routes requests through the proxy unless you provide an explicit `apiKey` prop.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/scripts/google-maps/2.api/1b.static-map.md` around lines 81 -
84, The "Proxy Support" paragraph uses passive voice; rewrite it in active voice
and keep the same meaning: state that when you configure googleMaps in
scripts.registry the system enables a server-side proxy, and clarify that the
component routes requests through that proxy unless the consumer provides an
explicit apiKey prop (reference: "googleMaps", "scripts.registry", "apiKey"
prop, "Proxy Support" heading).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/content/docs/4.migration-guide/1.v0-to-v1.md`:
- Line 338: Rewrite the passive sentence on line 338 to active voice; replace
"The new component can also be used standalone for store locators, contact
pages, and directions previews without loading the interactive Maps API." with
an active construction that names the actor (e.g., "You can use the new
component standalone for store locators, contact pages, and directions previews
without loading the interactive Maps API." or "Developers can use the new
component standalone for...") so the sentence is direct and in active voice.
- Around line 304-312: The documentation uses passive voice and the pipeline
flags it; update the section describing the Google Maps Static Placeholder to
active voice by rewriting sentences that currently use passive
construction—refer to the component and props by name
(ScriptGoogleMapsStaticMap, <ScriptGoogleMaps>, and the removed props
placeholderOptions, placeholderAttrs, aboveTheFold, plus the `#placeholder` slot)
and change wording to active phrasing (e.g., "We extracted the built-in static
map placeholder into the standalone ScriptGoogleMapsStaticMap component" and
"The following props were removed from <ScriptGoogleMaps>: ..."; ensure the
`#placeholder` slot description states explicitly that it no longer passes a
placeholder URL and is empty by default).

In `@docs/content/scripts/google-maps/2.api/1b.static-map.md`:
- Around line 48-69: The props table uses em-dashes (—) in the Default column
causing pipeline failures; edit the table rows for props like `center`,
`format`, `maptype`, `mapId`, `markers`, `path`, `visible`, `style`, `language`,
`region`, `signature`, `apiKey`, and `imgAttrs` and replace each em-dash with
either an explicit value like "none" or an empty cell (i.e., remove the em-dash)
so the Default column contains plain ASCII text; ensure all occurrences are
updated consistently across the table so the markdown renderer and pipeline no
longer error.

---

Nitpick comments:
In `@docs/content/scripts/google-maps/2.api/1b.static-map.md`:
- Around line 81-84: The "Proxy Support" paragraph uses passive voice; rewrite
it in active voice and keep the same meaning: state that when you configure
googleMaps in scripts.registry the system enables a server-side proxy, and
clarify that the component routes requests through that proxy unless the
consumer provides an explicit apiKey prop (reference: "googleMaps",
"scripts.registry", "apiKey" prop, "Proxy Support" heading).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6244561d-0002-4788-9647-df8f5848ba9b

📥 Commits

Reviewing files that changed from the base of the PR and between 335a953 and f64da30.

📒 Files selected for processing (5)
  • docs/content/docs/4.migration-guide/1.v0-to-v1.md
  • docs/content/scripts/google-maps/2.api/1.script-google-maps.md
  • docs/content/scripts/google-maps/2.api/1b.static-map.md
  • packages/script/src/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue
  • playground/pages/third-parties/google-maps/nuxt-scripts.vue
✅ Files skipped from review due to trivial changes (1)
  • playground/pages/third-parties/google-maps/nuxt-scripts.vue

@harlan-zw harlan-zw merged commit 6947c7a into main Mar 26, 2026
6 of 8 checks passed
@harlan-zw harlan-zw deleted the worktree-google-maps-static-map branch March 26, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant